using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;

namespace InverseKinematics
{
    /// <summary>
    /// This is the main type for your IK
    /// if the right mouse button is clicked once it swiches to 
    /// the inverse kinematics mode where the bones chain's endeffector
    /// is it clicked once again it switches back to the bone creation mode to add new bones to the chain
    /// checks if the left mouse button is clicked once it creates a new bone in the creational mode
    /// </summary>
    public class InverseKinematics : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;
        
        /// <summary>
        /// Texture of every bone in this environment
        /// </summary>
        Texture2D cubeTexture;

        /// <summary>
        /// Effect for displaying the cuboid
        /// </summary>
        BasicEffect cubeEffect;
        SpriteFont spriteFont;

        /// <summary>
        /// the plane where the generated ray by the mouse's postion intersects
        /// </summary>
        Plane plane = new Plane(new Vector4(0, 0, 1, 0));

        /// <summary>
        /// the elapsed time to count the frames per second
        /// </summary>
        public TimeSpan elapsedTime;

        int frameRate = 0;
        int frameCounter = 0;

        BoneChain bones = new BoneChain();

        ButtonState leftMouseLastState = ButtonState.Released;
        ButtonState rightMouseLastState = ButtonState.Released;
        bool inversekinematicsMode = false;
        // Position related variables
        private Camera camera;

        public Camera Camera
        {
            get { return camera; }
        }


 
        public InverseKinematics()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
        }

        /// <summary>
        /// Allows the game to perform any initialization it needs to before starting to run.
        /// This is where it can query for any required services and load any non-graphic
        /// related content.  Calling base.Initialize will enumerate through any components
        /// and initialize them as well.
        /// </summary>
        protected override void Initialize()
        {

         
            spriteBatch = new SpriteBatch(GraphicsDevice);
      
            base.Initialize();
        }

        /// <summary>
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// </summary>
        protected override void LoadContent()
        {
            // Create a new SpriteBatch, which can be used to draw textures.
            spriteBatch = new SpriteBatch(GraphicsDevice);
            cubeTexture = Content.Load<Texture2D>("Texture");
            camera = new Camera(graphics);
            
            this.IsMouseVisible = true;

            cubeEffect = new BasicEffect(GraphicsDevice);
            spriteFont = Content.Load<SpriteFont>("Font");
            // TODO: use this.Content to load your game content here
        }



        /// <summary>
        /// UnloadContent will be called once per game and is the place to unload
        /// all content.
        /// </summary>
        protected override void UnloadContent()
        {
            // TODO: Unload any non ContentManager content here
        }

        /// <summary>
        /// Allows the game to run logic such as updating the world,
        /// checking for collisions, gathering input, and playing audio.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Update(GameTime gameTime)
        {
            // Allows the game to exit
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
                this.Exit();

            // gets the point of the ray which is intersecting the field plane
            Vector3 destination = GetMousePosition3D();
            
            // checks if the left mouse button is clicked once it creates a new bone
            bool leftClick = Mouse.GetState().LeftButton == ButtonState.Released && leftMouseLastState == ButtonState.Pressed;
            
            // if the right mouse button is clicked once it swiches to 
            //the inverse kinematics mode where the bones chain's endeffector
            // is it clicked once again it switches back to the bone creation mode to add new bones to the chain
            if( Mouse.GetState().RightButton == ButtonState.Released && rightMouseLastState == ButtonState.Pressed)
                inversekinematicsMode = !inversekinematicsMode;

            // bone creation and inverse kinematics by using the ccd
            if (inversekinematicsMode) bones.CalculateCCD(destination,gameTime);
            else bones.CreateBone(destination, leftClick);

            leftMouseLastState = Mouse.GetState().LeftButton;
            rightMouseLastState = Mouse.GetState().RightButton;
            bones.Update();


            //FPSCounter
            elapsedTime += gameTime.ElapsedGameTime;

            if (elapsedTime > TimeSpan.FromSeconds(1))
            {
                elapsedTime -= TimeSpan.FromSeconds(1);
                frameRate = frameCounter;
                frameCounter = 0;
            }

            //Console.WriteLine(destination.ToString());
            base.Update(gameTime);
            
        }


        /// <summary>
        /// the mouse position on the screen
        /// </summary>
        /// <returns>the position of the screen as a vector with 2 dimension</returns>
        public Vector2 GetMousePosition2D()
        {
            return new Vector2(Mouse.GetState().X, Mouse.GetState().Y);
        }

        /// <summary>
        /// the mouse position in 3d space
        /// </summary>
        /// <returns> returns the position(a vector with 3 dimensions) where the generated mouse ray intersects with the plane of this class </returns>
        public Vector3 GetMousePosition3D()
        {
            Vector2 Position = GetMousePosition2D();
            Vector3 nearSource = new Vector3(Position, 0f);
            Vector3 farSource = new Vector3(Position, 1f);

            Vector3 nearPoint = GraphicsDevice.Viewport.Unproject(nearSource, Camera.CameraProjectionMatrix, Camera.CameraViewMatrix, Matrix.Identity);
            Vector3 farPoint  = GraphicsDevice.Viewport.Unproject(farSource, Camera.CameraProjectionMatrix, Camera.CameraViewMatrix, Matrix.Identity);

            Vector3 direction = farPoint - nearPoint;
            direction.Normalize();

            Ray ray = new Ray(nearPoint, direction);
            

            float denominator = Vector3.Dot(plane.Normal, ray.Direction);
            float numerator = Vector3.Dot(plane.Normal, ray.Position) + plane.D;
            float t = -(numerator / denominator);
            // calculate the picked position on the y = 0 plane        
            Vector3 pickedPosition = nearPoint + direction * t;
            return pickedPosition;
      
        }

        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Draw(GameTime gameTime)
        {
            graphics.GraphicsDevice.Clear(Color.Black);

            // TODO: Add your drawing code here
            cubeEffect.World = Matrix.Identity;

            // Set the View matrix which defines the camera and what it's looking at
            cubeEffect.View = camera.CameraViewMatrix;

            // Set the Projection matrix which defines how we see the scene (Field of view)
            cubeEffect.Projection = camera.CameraProjectionMatrix;

            // Enable textures on the Cube Effect. this is necessary to texture the model
            cubeEffect.TextureEnabled = true;
            cubeEffect.Texture = cubeTexture;

            // Enable some pretty lights
            cubeEffect.EnableDefaultLighting();

            // apply the effect and render the cube
            foreach (EffectPass pass in cubeEffect.CurrentTechnique.Passes)
            {
                pass.Apply();

                bones.Draw(graphics.GraphicsDevice);
                
            }

            //draws the frames per second cound
            frameCounter++;

            string fps = string.Format("fps: {0}", frameRate);

            spriteBatch.Begin();
            spriteBatch.DrawString(spriteFont, fps, new Vector2(32, 32), Color.White);

            spriteBatch.End();

            base.Draw(gameTime);
        }

        
    }
}
